package org.unit.testmgmt.base;

import java.util.Vector;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtension;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.IExtensionRegistry;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.wizard.WizardDialog;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IWorkbenchWindow;
import org.unit.testmgmt.generator.GeneratorUtils;
import org.unit.testmgmt.generator.ITestClassGenerator;
import org.unit.testmgmt.generator.ITestSuitesGenerator;
import org.unit.testmgmt.generator.TestClassGenerator;
import org.unit.testmgmt.generator.TestSuitesGenerator;
import org.unit.testmgmt.generator.wizard.GeneratorWizard;
import org.unit.testmgmt.messages.Messages;
import org.unit.testmgmt.preferences.UTMPreferences;
import org.unit.testmgmt.tml.Settings;
import org.unit.testmgmt.tml.TMLProcessor;
import org.unit.testmgmt.tml.Test;
import org.unit.testmgmt.tml.UTMElements;
import org.unit.testmgmt.tml.UTMElements.UTMClassesAndPackages;
import org.unit.testmgmt.tml.UTMElements.UTMConstructorsAndMethods;
import org.unit.testmgmt.tml.UTMElements.UTMProjects;
import org.unit.testmgmt.tml.UTMModel;

/**
 * Main controller for all test-management-commands and actions.
 * 
 * @author Robert Streng
 */
public class UTMController implements IUTMConstants {

	private Vector<ITestClassGenerator> testClassGenerators = null;

	private IFile generatedTestClass = null;

	private Vector<ITestSuitesGenerator> testSuitesGenerators = null;

	private boolean customGeneratorsInitialized = false;
	private Vector<ITestClassGenerator> customTestClassGenerators;
	private Vector<ITestSuitesGenerator> customTestSuitesGenerators;

	private boolean error = false;

	private Exception errorException;

	/**
	 * Getter for the test-class-generator.
	 * 
	 * @return test-class-generator
	 * @throws CoreException
	 */
	protected Vector<ITestClassGenerator> getTestClassGenerators() throws CoreException {

		Vector<ITestClassGenerator> customTestClassGenerators = getCustomTestClassGenerators();
		if (customTestClassGenerators.size() > 0) {
			return customTestClassGenerators;
		}

		if (testClassGenerators == null) {
			testClassGenerators = new Vector<ITestClassGenerator>();
			testClassGenerators.add(new TestClassGenerator());
		}
		return testClassGenerators;
	}

	private void initCustomGenerators() throws CoreException {
		if (!customGeneratorsInitialized) {
			customGeneratorsInitialized = true;
			customTestClassGenerators = new Vector<ITestClassGenerator>();
			customTestSuitesGenerators = new Vector<ITestSuitesGenerator>();

			IExtensionRegistry registry = Platform.getExtensionRegistry();
			IExtensionPoint point = registry.getExtensionPoint("org.unit.testmgmt.generator");
			if (point != null) {
				String elementName;

				for (IExtension extension : point.getExtensions()) {
					for (IConfigurationElement configElement : extension.getConfigurationElements()) {
						elementName = configElement.getName();
						Object generator = configElement.createExecutableExtension("class");
						if (generator != null) {
							if ("testclass_generator".equals(elementName)) {
								customTestClassGenerators.add((ITestClassGenerator) generator);
							}
							else if ("testsuites_generator".equals(elementName)) {
								customTestSuitesGenerators.add((ITestSuitesGenerator) generator);
							}
						}
					}
				}
			}
		}
	}

	private Vector<ITestClassGenerator> getCustomTestClassGenerators() throws CoreException {
		initCustomGenerators();
		return customTestClassGenerators;
	}

	private Vector<ITestSuitesGenerator> getCustomTestSuitesGenerators() throws CoreException {
		initCustomGenerators();
		return customTestSuitesGenerators;
	}

	/**
	 * Getter for the test-suite-generator.
	 * 
	 * @return test-suites-generator
	 * @throws CoreException
	 */
	protected Vector<ITestSuitesGenerator> getTestSuitesGenerators() throws CoreException {
		Vector<ITestSuitesGenerator> customTestSuitesGenerators = getCustomTestSuitesGenerators();
		if (customTestSuitesGenerators.size() > 0) {
			return customTestSuitesGenerators;
		}

		if (testSuitesGenerators == null) {
			testSuitesGenerators = new Vector<ITestSuitesGenerator>();
			testSuitesGenerators.add(new TestSuitesGenerator());
		}

		return testSuitesGenerators;
	}

	/**
	 * Generates a test-class.
	 * 
	 * @param activeWorkbenchWindow
	 * @param selection
	 * @return true, if the test-class is successful generated. False otherwise.
	 * @throws UTMException
	 * @throws UTMWarning
	 */
	public boolean generateTestclass(IWorkbenchWindow activeWorkbenchWindow,
			IStructuredSelection selection) throws UTMException, UTMWarning {
		UTMElements utmElements = detectUTMElements(selection, null);
		return generateTestclass(activeWorkbenchWindow, utmElements);
	}

	/**
	 * Generates a test-class.
	 * 
	 * @param activeWorkbenchWindow
	 * @param fileEditorInput
	 * @return true, if the test-class is successful generated. False otherwise.
	 * @throws UTMException
	 * @throws UTMWarning
	 */
	public boolean generateTestclass(IWorkbenchWindow activeWorkbenchWindow,
			IFileEditorInput fileEditorInput) throws UTMException, UTMWarning {
		UTMElements utmElements = detectUTMElements(null, fileEditorInput);
		return generateTestclass(activeWorkbenchWindow, utmElements);
	}

	/**
	 * Generates a test-class.
	 * 
	 * @param activeWorkbenchWindow
	 * @param utmElements
	 * @return true, if the test-class is successful generated. False otherwise.
	 * @throws UTMException
	 * @throws UTMWarning
	 */
	protected boolean generateTestclass(final IWorkbenchWindow activeWorkbenchWindow,
			UTMElements utmElements) throws UTMException, UTMWarning {
		if (utmElements == null) {
			throw new UTMWarning("No elements found! Perhaps baseclass changed.");
		}

		UTMProjects projects = utmElements.getProjects();
		if (projects == null) {
			throw new UTMWarning("No project found! Perhaps baseclass changed.");
		}

		try {

			IJavaProject testProject = projects.getTestProject();

			UTMClassesAndPackages classesAndPackages = utmElements.getClassesAndPackages();
			String testPackageName = classesAndPackages.getTestPackage().getElementName();
			String testClassName = classesAndPackages.getTestClassName();
			ICompilationUnit testClass = classesAndPackages.getTestClass();

			TMLProcessor tmlProcessor = new TMLProcessor(testProject, testPackageName,
					testClassName);

			// get the TML-file
			Test tmlTest = null;

			if (testClass != null && testClass.exists()) {
				tmlTest = tmlProcessor.readTmlFile();
			}

			// create the model
			final UTMModel model = new UTMModel(utmElements, tmlTest);

			// Open wizard
			if (!runGeneratorWizard(model, activeWorkbenchWindow)) {
				return false;
			}

			// update TML-file
			tmlProcessor.writeTmlFile(model.getTmlTest());

			// save and close opened test-class-file
			GeneratorUtils.saveAndCloseEditor(activeWorkbenchWindow.getShell(), testClassName);

			// Generate test-elements
			IRunnableWithProgress runnableWithProgress = new IRunnableWithProgress() {

				@Override
				public void run(IProgressMonitor monitor) {
					try {
						IFile generatedTestClass = null;
						for (ITestClassGenerator testClassGenerator : getTestClassGenerators()) {
							generatedTestClass = testClassGenerator.generate(model, monitor);
						}
						setGeneratedTestClass(generatedTestClass);

						monitor.done();
					}
					catch (Exception e) {
						setError(true, e);
					}
				}
			};

			setError(false);
			try {
				activeWorkbenchWindow.run(true, true, runnableWithProgress);
			}
			catch (Exception ex) {
				throw new UTMException(ex);
			}

			if (isError()) {
				throw new UTMException(errorException);
			}

			// generate test-suites
			Settings settings = model.getTmlTest().getSettings();
			if (settings != null) {
				if (settings.isTestsuites()) {
					generateTestSuites(utmElements);
				}
			}
			else {
				generateTestSuites(utmElements);
			}

			// open in editor
			openInEditor(activeWorkbenchWindow.getShell(), getGeneratedTestClass());

		}
		catch (UTMException ex) {
			throw ex;
		}
		catch (Exception ex) {
			throw new UTMException(ex);
		}

		return true;
	}

	protected void setError(boolean error, Exception ex) {
		setError(error);
		this.errorException = ex;
	}

	/**
	 * Generates the test-suites
	 * 
	 * @param utmElements
	 * @throws CoreException
	 */
	protected void generateTestSuites(UTMElements utmElements) throws CoreException {
		for (ITestSuitesGenerator testSuitesGenerator : getTestSuitesGenerators()) {
			testSuitesGenerator.generateTestSuites(utmElements);

		}
	}

	private IFile getGeneratedTestClass() {
		return this.generatedTestClass;
	}

	protected void setGeneratedTestClass(IFile generatedTestClass) {
		this.generatedTestClass = generatedTestClass;
	}

	protected void openInEditor(Shell shell, IFile generatedTestclass) {
		GeneratorUtils.openInEditor(shell, generatedTestclass);
	}

	/**
	 * Detects all the necessary UTM-Elements: project, package and class for the base and test.
	 * 
	 * @param selection
	 * @param fileEditorInput
	 * @return detected UTM-Elements
	 * @throws UTMException
	 * @throws UTMWarning
	 */
	private UTMElements detectUTMElements(IStructuredSelection selection,
			IFileEditorInput fileEditorInput) throws UTMException, UTMWarning {

		UTMElements utmElements = new UTMElements();

		// get active editor if nothing selected
		if ((selection == null || selection.isEmpty()) && fileEditorInput == null) {
			IEditorPart activeEditor = GeneratorUtils.getActiveEditor();
			IEditorInput editorInput = activeEditor.getEditorInput();

			if (editorInput instanceof IFileEditorInput) {
				fileEditorInput = (IFileEditorInput) editorInput;
			}
			else {
				throw new UTMWarning(Messages.General_warning_nothing_selected);
			}
		}

		// projects
		utmElements.setProjects(getProjects(selection, fileEditorInput));

		// classes and packages
		try {
			utmElements.setClassesAndPackages(getClassesAndPackages(selection, fileEditorInput,
					utmElements.getProjects()));
		}
		catch (UTMWarning e) {
			throw e;
		}
		catch (UTMException e) {
			throw e;
		}
		catch (Exception e) {
			throw new UTMException(e);
		}

		if (!utmElements.getClassesAndPackages().getTestBase().exists()) {
			return null;
		}

		// base-class constructors and methods
		utmElements.setConstructorsAndMethods(getConstructorsAndMethods(utmElements
				.getClassesAndPackages().getTestBase()));

		return utmElements;
	}

	private UTMConstructorsAndMethods getConstructorsAndMethods(ICompilationUnit baseclass) {

		Vector<IMethod> baseclassConstructors = new Vector<IMethod>();
		Vector<IMethod> baseclassMethods = new Vector<IMethod>();

		try {
			for (IType type : baseclass.getTypes()) {
				for (IMethod method : type.getMethods()) {
					if (method.isConstructor())
						baseclassConstructors.add(method);
					else
						baseclassMethods.add(method);
				}
			}
		}
		catch (JavaModelException e) {
			throw new RuntimeException(e);
		}

		UTMConstructorsAndMethods constructorsAndMethods = new UTMElements().new UTMConstructorsAndMethods();
		constructorsAndMethods.setBaseClassConstructors(baseclassConstructors);
		constructorsAndMethods.setBaseClassMethods(baseclassMethods);

		return constructorsAndMethods;
	}

	private UTMClassesAndPackages getClassesAndPackages(ISelection selection,
			IFileEditorInput fileEditorInput, UTMProjects projects) throws UTMWarning, Exception {
		UTMClassesAndPackages utmClassesAndPackages = new UTMElements().new UTMClassesAndPackages();

		Vector<ICompilationUnit> cuList = GeneratorUtils.getCompilationUnits(selection,
				fileEditorInput);
		ICompilationUnit baseCu;
		IPackageFragment basePackage;

		ICompilationUnit testCu;
		String baseCuName, testCuName;
		IPackageFragment testPackage;

		String testPackagePostfix = UTMPreferences.getTestPackagePostfix();
		String testClassPostfix = UTMPreferences.getTestClassPostfix();

		String baseProjectName = "";
		String basePackageName = "";
		String testProjectName = "";
		String testPackageName = "";

		if (cuList.size() == 0) {
			throw new UTMWarning(Messages.General_warning_nothing_selected);
		}

		if (projects.isBaseProjectSelected()) {
			baseCu = cuList.get(0);
			basePackage = GeneratorUtils.getPackage(baseCu);
			baseProjectName = projects.getBaseProject().getElementName();
			basePackageName = basePackage.getElementName();
			testProjectName = null;

			if (projects.getTestProject() != null)
				testProjectName = projects.getTestProject().getElementName();

			if ("".equals(basePackageName)) {
				testPackageName = testPackagePostfix;
			}
			else if (basePackageName.startsWith(baseProjectName)) {
				testPackageName = basePackageName.replace(baseProjectName, testProjectName);
			}
			else {
				testPackageName = basePackageName + "." + testPackagePostfix;
			}

			testPackage = GeneratorUtils.createPackage(projects.getTestProject(), testPackageName);

			baseCuName = baseCu.getElementName().replace(".java", "");
			testCuName = baseCuName + testClassPostfix;
			testCu = testPackage.getCompilationUnit(testCuName + ".java");
		}
		else {
			testCu = cuList.get(0);
			testPackage = GeneratorUtils.getPackage(testCu);
			baseProjectName = projects.getBaseProject().getElementName();
			testProjectName = projects.getTestProject().getElementName();
			testPackageName = testPackage.getElementName();

			if (testPackagePostfix.equals(testPackageName)) {
				basePackageName = ""; // default package
			}
			else if (testPackageName.startsWith(testProjectName)) {
				basePackageName = testPackageName.replace(testProjectName, baseProjectName);
			}
			else {
				basePackageName = testPackageName.replace("." + testPackagePostfix, "");
			}

			basePackage = GeneratorUtils.createPackage(projects.getBaseProject(), basePackageName);

			testCuName = testCu.getElementName().replace(".java", "");
			baseCuName = testCu.getElementName().replace(testClassPostfix + ".java", "");
			baseCu = basePackage.getCompilationUnit(baseCuName + ".java");
		}

		utmClassesAndPackages.setBasePackage(basePackage);
		utmClassesAndPackages.setTestBase(baseCu);
		utmClassesAndPackages.setBaseClassName(baseCuName);
		utmClassesAndPackages.setTestPackage(testPackage);
		utmClassesAndPackages.setTestClass(testCu);
		utmClassesAndPackages.setTestClassName(testCuName);

		return utmClassesAndPackages;

	}

	private UTMProjects getProjects(ISelection selection, IFileEditorInput fileEditorInput)
			throws UTMException, UTMWarning {
		UTMProjects projects = new UTMElements().new UTMProjects();

		// get project via selection
		IJavaProject project = GeneratorUtils.getProject(selection, fileEditorInput);

		if (project == null) {
			return null;
		}

		String projectName = project.getElementName();
		String testProjectPostfix = UTMPreferences.getTestProjectPostfix();
		String testPackagePostfix = UTMPreferences.getTestPackagePostfix();

		if (projectName.endsWith(testProjectPostfix)) {
			projects.setBaseProjectSelected(false);

			projects.setTestProject((IJavaProject) project);

			// get base project
			int lastIx = projectName.lastIndexOf("." + testPackagePostfix);
			if (lastIx < 1) {
				throw new UTMWarning(Messages.General_warning_nothing_selected);
			}
			else {
				projectName = projectName.substring(0, lastIx);
			}

			projects.setBaseProject((IJavaProject) GeneratorUtils.getProject(projectName));
		}
		else {
			projects.setBaseProjectSelected(true);
			projects.setBaseProject((IJavaProject) project);

			// get test project
			projectName += "." + testPackagePostfix;
			projects.setTestProjectName(projectName);
			projects.setTestProject(GeneratorUtils.getProject(projectName, true, project));
		}

		return projects;
	}

	/**
	 * Runs the generator-wizard.
	 * 
	 * @param model
	 * @param workbenchPart
	 * @return boolean true
	 */
	protected boolean runGeneratorWizard(UTMModel model, IWorkbenchWindow workbenchPart) {

		GeneratorWizard wizard = new GeneratorWizard(model);

		WizardDialog dialog = new WizardDialog(workbenchPart.getShell(), wizard);
		dialog.create();

		wizard.initPages();

		dialog.open();

		if (wizard.isFinished()) {
			return true;
		}

		return false;
	}

	/**
	 * Indicates if an error occurred.
	 * 
	 * @return true if an error occurred
	 */
	public boolean isError() {
		return error;
	}

	/**
	 * Sets the error-flag.
	 * 
	 * @param error
	 */
	public void setError(boolean error) {
		this.error = error;
	}

}
